#include "Context.hpp"

#include "Shader.hpp"

#include "Scene.hpp"
#include "Camera.hpp"
#include "UserInterface.hpp"
#include "Timer.hpp"

#include <string>

Context g_context;

struct ShaderFiles {
  GLenum type;
  std::string fileName;
};

struct ProgramInfo {
  ShaderFiles shaderFiles[2];
  Context::ProgramId programId;
};

static const ProgramInfo g_programInfos[] =
{
  { 
    {
      {GL_VERTEX_SHADER,   "resources/shaders/shader.vert"},
      {GL_FRAGMENT_SHADER, "resources/shaders/shader.frag"}
    },
    Context::Normal
  },
  {
    {
      {GL_VERTEX_SHADER,   "resources/shaders/Sky.vert"},
      {GL_FRAGMENT_SHADER, "resources/shaders/Sky.frag"}
    },
    Context::SkyBox
  },
  {
    {
      {GL_VERTEX_SHADER,   "resources/shaders/Water.vert"},
      {GL_FRAGMENT_SHADER, "resources/shaders/Water.frag"}
    },
    Context::Water
  },
  {
    {
      {GL_VERTEX_SHADER,   "resources/shaders/Fairy.vert"},
      {GL_FRAGMENT_SHADER, "resources/shaders/Fairy.frag"}
    },
    Context::Fairy
  },
  {
    {
      {GL_VERTEX_SHADER,   "resources/shaders/Wings.vert"},
      {GL_FRAGMENT_SHADER, "resources/shaders/Wings.frag"}
    },
    Context::Wings
  }
};

void Context::initFire()
{
  
  static const ShaderFiles fireShaderFiles[] = 
  {
    {GL_VERTEX_SHADER,   "resources/shaders/FireUpdate.vert"},
    {GL_VERTEX_SHADER,   "resources/shaders/FireRender.vert"},
    {GL_FRAGMENT_SHADER, "resources/shaders/Fire.frag"}
  };
  
  enum {UpdatePass = 0, RenderPass, NumPasses};
  
  Program * programs = new Program[NumPasses];
  programs[UpdatePass].create();
  programs[RenderPass].create();
  
  unsigned numShaders = sizeof(fireShaderFiles)/sizeof(ShaderFiles);
  Shader * shaders = new Shader[numShaders];
  for (unsigned i = 0; i < numShaders; ++i) {
    shaders[i].load(fireShaderFiles[i].type,
                    fireShaderFiles[i].fileName);
    
    shaders[i].compile();

    
  }
  
  programs[UpdatePass].attachShader(&shaders[0]);
  programs[RenderPass].attachShader(&shaders[1]);
  programs[RenderPass].attachShader(&shaders[2]);
  
  const char * outputNames[] = {
    "Position", "StartTime", "Velocity"
  };
  
  glTransformFeedbackVaryings(programs[UpdatePass].getHandle(),
                              sizeof(outputNames)/sizeof(const char *),
                              outputNames,
                              GL_SEPARATE_ATTRIBS);
  
  programs[UpdatePass].link();
  programs[RenderPass].link();

  delete [] shaders;
    
  m_progs[Context::FireUpdate] = &programs[UpdatePass];
  m_progs[Context::FireRender] = &programs[RenderPass];
  
}

void Context::initPrograms()
{
  
  for (unsigned programIndex = 0;
       programIndex < sizeof(g_programInfos)/sizeof(ProgramInfo);
       ++programIndex) {
    Program * program = new Program();
    program->create();
    
#warning take new outside of loop
    Shader * shaders = new Shader[2];
    
    for (int shaderIndex = 0; shaderIndex < 2; ++shaderIndex) {
      shaders[shaderIndex].load(g_programInfos[programIndex].shaderFiles[shaderIndex].type,
                                g_programInfos[programIndex].shaderFiles[shaderIndex].fileName);

      shaders[shaderIndex].compile();

      program->attachShader(&shaders[shaderIndex]);
    }
    
    program->link();

    delete [] shaders;
    
    m_progs[g_programInfos[programIndex].programId] = program;

  }
  
  //initFire();

  m_progs[Normal]->use();
  
  
  //m_currentProgram = m_progs[Normal]->use();
  //m_currentProgram->use();
  
}
/*
void Context::skyInitProgram()
{
  m_program.create();
  
  Shader * shaders = new Shader[2];
  
  struct {
    GLenum type;
    std::string fileName;
  } shaderFiles[2] = {
    {GL_VERTEX_SHADER,   "resources/shaders/Sky.vert"},
    {GL_FRAGMENT_SHADER, "resources/shaders/Sky.frag"}
  };

  for (int i = 0; i < 2; i++) {
    shaders[i].load(shaderFiles[i].type,
                    shaderFiles[i].fileName);

    shaders[i].compile();

    m_program.attachShader(&shaders[i]);
  }

  m_program.link();

  delete [] shaders;
  m_program.use();
  
}
*/
void Context::init()
{
  
  initPrograms();
  
  /*
  int ul_proj  = m_currentProgram->getUniformLocation("MatrixProjection");
  int ul_view  = m_currentProgram->getUniformLocation("MatrixView");
  int ul_model = m_currentProgram->getUniformLocation("MatrixModel");
  */
  /*
  float fovy = 50.0f;
  float aspect = 800.0f/600.0f;

  float near = 0.1f;
  float far = 1000.0f;
  mat4 proj = glm::perspective(fovy, aspect, near, far);
  //glUniformMatrix4fv(ul_proj, 1, false, &proj[0][0]);
  
  vec3 eye(10.0f, 2.0f, 10.0f);
  vec3 center(0.0f, 0.0f, 0.0f);
  vec3 up(0.0f, 1.0f, 0.0f);
  mat4 view = glm::lookAt(eye, center, up);
  //glUniformMatrix4fv(ul_view, 1, false, &view[0][0]);
  
  mat4 model(1.0f);
  //glUniformMatrix4fv(ul_model, 1, false, &model[0][0]);
  */

  
  m_scene = new Scene();
  
  m_scene->update();
  
  m_camera = new Camera();
  
  
  UserInterface::initCallbacks();
  
  Timer::init();
  
  
}

Player* Context::getPlayer()
{
  return m_scene->m_player;
}

Segway* Context::getSegway()
{
  return m_scene->m_segway;
}

Object* Context::getUpdater()
{
  return m_scene->m_messenger;
}

float Context::getHeight(float x, float z)
{
  return m_scene->m_terrain->getHeight(x,z);
}

bool Context::boundcheck(vec3 pos)
{
  return m_scene->m_terrain->free(pos);
}

Program * Context::useProgram(ProgramId programId)
{
  if (m_currentProgramId == programId)
    return m_currentProgram;
  m_currentProgramId = programId;
  m_currentProgram = m_progs[programId];
  m_currentProgram->use();
  return m_currentProgram;
}

Object* Context::near(vec3 pos, const std::string & objname)
{
  return m_scene->m_terrain->near(pos, objname);
}